Skip to content

IM3-263: feat(market): 4 spot 기준 경쟁점 표시 통일 + 시뮬 결정성 + AI 요약 레이아웃#204

Merged
yezin013 merged 14 commits into
devfrom
IM3-263-ai-summary-layout
May 6, 2026
Merged

IM3-263: feat(market): 4 spot 기준 경쟁점 표시 통일 + 시뮬 결정성 + AI 요약 레이아웃#204
yezin013 merged 14 commits into
devfrom
IM3-263-ai-summary-layout

Conversation

@yezin013

@yezin013 yezin013 commented May 6, 2026

Copy link
Copy Markdown
Collaborator
  • 공실 spot 1~4 모두 기준으로 경쟁점 풀 합집합 + within/외부 분류 (winner centroid →
    spot 좌표로 통일)

    • 시뮬마다 경쟁업체 변동 회귀 fix (vacancy_spots ORDER BY 부재 + race condition 제거)
    • UI 회귀 fix — 팝업 가려짐, 마커 무한 cleanup, top5 빨간 삼각형 누락
    • AI 요약 레이아웃 + demographic 확장 (이전 세션분)

    주요 변경

    Backend

    • _resolve_top_spot_coords — 4 spot 좌표 list (winner score → top3 listing_count →
      50m dedup)
    • _query_kakao_store_by_coord — spot 좌표 SQL (ORDER BY kakao_id 결정성)
    • _collect_all_competitor_locations spot_coords 인자 추가 (각 spot 1.5km × 800
      합집합)
    • _load_vacancy_spots SQL ORDER BY id (PG 자연순서 비결정 root cause fix)
    • competitor_intel._matched score 1위 정렬 (frontend bestVacancy 와 분석 기준 통일)

    Frontend

    • MarketMap within = 4 spot 중 최단거리 ≤ radius
    • InfoWindow → CustomOverlay zIndex=100 (팝업 가려짐 fix)
    • top5 노란 번호 badge overlay (클릭 가능, 빨간 삼각형은 보존)
    • legend "내부/총" 4 spot union 기준
    • buildCompetitors cap 200 → 1000 (spot 2,3,4 가장자리 누락 차단)
    • topCompetitors useMemo (마커 무한 cleanup 회귀 fix)
    • "추천 입지 안내 — 매물 자동 보정" 큰 amber 배너 (winner ≠ spot 1위 동 케이스)
    • "동 한눈에" 공실률을 analysisDong (spot 1위 동) 기준으로

    Test plan

    • backend 재기동 + Redis FLUSHDB 후 동일 입력으로 시뮬 2회 — 경쟁점 set 동일
    • backend 콘솔 [all_competitors:spots] N spot ... 최종 합집합 M개 로그 (M≥200)
    • 4 spot 각 500m 반경 안 매장이 모두 진한 빨간 삼각형
    • 노란 번호 badge (top5) 클릭 시 popup 정상
    • 다른 경쟁점 마커 클릭 popup 가려짐 없음
    • winner ≠ spot 1위 동 케이스에서 amber 안내 배너 표시
    • 동 한눈에 공실률이 analysisDong 기준
    • vacancy_spots[0] 좌표 시뮬 2회 동일

yezin013 and others added 14 commits May 6, 2026 11:50
증상: winner 동(예: 망원2동) 안 vacancy_spot 0건 시 1~4위 자리 비어 1순위
펄싱 핀만 (그것도 동 centroid 가짜 위치) 표시되던 회귀.

원인: buildBestVacancies 가 winner 동만 필터 → spot 부족 시 fallback 없음.

수정: winner 동 spot 우선 (backend score) + 부족분은 top3 동 spot 으로 채움
(listing_count 정렬). 항상 최대 4개 반환, 50m 근접 dedup 유지.

- winnerSorted = winner 동 spot, score 정렬
- top3Sorted = top3 동 spot, listing_count 정렬 (score 없음)
- merged → dedup → top 4

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- backend/src/services/corp_brand_resolver.py: users.company_name → ftc_brand_franchise.corpNm 매칭, get_corp_industries / resolve_brand_for_industry
- backend/src/main.py: GET /corp/operated-industries (JWT 기반 자동 추출), _validate_and_resolve_brand 헬퍼 + 7 endpoint Depends(get_optional_user) 통합
- backend/src/schemas/simulation_input.py: biz_number optional 필드
- frontend/src/api/client.ts: getOperatedIndustries() + OperatedIndustriesResponse 타입
- frontend/src/App.tsx: mount 시 fetch + 운영 외 frontend 라벨 disable + line-through + click toast

다업종 corp ((주)더본코리아 8업종 27 brand 등) 시 운영 외 업종 dropdown 차단.
비회원/CORP_NOT_IN_FTC: industries=null 반환 → 모든 업종 허용 (graceful degrade).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
증상: winner=망원2동(매물 0건), spot 1위=망원1동(인접 동 매물) 케이스에서
사용자가 두 정보의 misalign 을 인지 못 함.

수정: TARGET 박스 아래에 amber warning 라벨 표시.
- 조건: bestVacancy.dongName !== winner_district 일 때만
- 메시지: "추천 1순위 ○○동에 임대 매물이 없어, 인접 후보 동 △△동의 매물에서
  공실 spot 을 자동 추천합니다"

분석 기준 동 변경 (옵션 2) 은 별도 작업으로 진행.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
증상: winner=망원2동(매물 0건) 케이스에서 PHASE 2 LLM 에이전트들 (market/
population/legal/demographic/trend/competitor) 이 망원2동 기준으로 분석 →
화면 표시 분석 결과가 spot 1위 동(망원1동) 와 다른 동 가리켜 misalign.

수정: PHASE 2 진입 시 target_district 결정 로직 변경.
- 1순위: winner 동 spot 중 score(또는 listing_count) 1위 → 그 동
- 2순위: top3 동 spot 중 listing_count 1위 (winner 동 매물 0건일 때)
- fallback: winner_district

frontend buildBestVacancies 와 동일 로직 — 분석 기준과 화면 표시 일관.

winner_district 변수는 그대로 보존 → 노란 강조·라벨·랭킹 정보 미영향.
캐시 키는 target_district 따라 자동 분리 (spot 동 캐시 신규 산출).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
증상:
- 주요 경쟁점 카드: '컴포즈커피 망원시장점 50m' (분석 동 centroid 기준)
- 지도 마커: 그 매장이 안 보임 (all_competitor_locations 누락)
- 주요 경쟁점 카드의 거리가 spot 1위 좌표 기준 아님 (예: 무흐 82m 누락)

원인: 두 데이터 소스가 분리됨.
- competitor_intel.samples: 분석 동 centroid 500m, top 20 cap
- all_competitor_locations: 4동 centroid 1.5km, 357개 (samples 외 매장)

수정:
- MapSection.buildCompetitors: all_competitor_locations + samples union → place_name + 좌표 dedup → 200 cap.
  지도 마커에 두 소스 매장 모두 포함 (컴포즈 망원시장점 누락 해소).
- MarketTab.topCompetitors: spot 1위 좌표(vacancy_spots top1) 결정 후 모든 매장의 haversine 재계산 → 가까운 5개.
  spot 1위 좌표 기준이라 무흐 82m 같은 진짜 가까운 매장이 카드 상단에 표시.

→ 카드와 지도 모두 spot 1위 좌표 기준 일관된 정보.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
증상:
1. '동 한눈에' 의 공실률이 winner 동(망원2동) 데이터로 표시
   → district_rankings[winner].vacancy_rate 직접 참조
2. 마켓맵 위 작은 안내 라벨이 한눈에 안 들어옴

수정:
1. MarketTab 에 analysisDong 변수 추가 (spot 1위 동 우선, fallback winner)
   _spot1Info 에 dongName 포함 → analysisDong 추출
   WinnerDistrictSummary props 에 analysisDong 전달, district_rankings 검색 시 사용
   → 공실률 = analysisDong (망원1동) 데이터로 일관

2. 안내 라벨 → 큰 amber 배너로 강조
   · AlertTriangle 아이콘 + "추천 입지 안내 — 매물 자동 보정" 헤더
   · "○○동에 매물 없어 △△동의 매물에서 추천" 본문
   · 추가 라인: "분석 결과/주요 경쟁점/동 한눈에 모두 △△동 기준으로 도출"
   · border-2 + box-shadow glow 로 시각 강조

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
이슈 ① — spot 1위 기준 반경 500m 경쟁업체 매 시뮬마다 바뀜
  Root cause: _load_vacancy_spots SQL 에 ORDER BY 없음 → PG 자연순서 비결정
  → vacancy_spots ordering 매번 다름 → competitor_intel _matched[0] 비결정
  → spot_lat/lon 캐시 키 매번 다름 → 카니발 분석/sample 새로 생성
  Fix A: ORDER BY NaverVacancy.id 추가 (district_ranking.py)
  Fix B: competitor_intel _matched 를 score 1위 (없으면 listing_count) 정렬
        → frontend bestVacancy 좌표와 backend 분석 기준점 완전 통일

이슈 ② — 주요 경쟁점 5개 지도에 강조 표시
  · MarketMap topCompetitors prop 추가 (rank, lat, lng)
  · 좌표 매칭 시 노란 큰 동그라미(28px) + 번호 라벨 + zIndex=6
  · MapSection → MarketMap 흐름 연결, MarketTab 에서 topCompetitors 전달
  · 범례에 "주요 경쟁점 (1~5위)" 항목 추가

이슈 ③ — 다른 경쟁점 마커 팝업창 가려져 안 보임
  Root cause: Kakao InfoWindow 가 CustomOverlay (펄싱핀 zIndex=5/번호핀=5) 에 가려짐
  Fix: InfoWindow → CustomOverlay (zIndex=100) 로 통일
       · X 버튼 직접 부착 (CustomOverlay 엔 close UI 없음)
       · 마커 위쪽 화살표 + transform translateY(-10px) 로 깔끔한 배치
       · 경쟁점/자사매장 두 핸들러 모두 openPopup 헬퍼로 통합

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
회귀:
  이전 commit 에서 top5 매장을 노란 동그라미로 '치환' 해서 반경 안 빨간 삼각형이
  5개 사라진 것처럼 보이는 문제. 사용자 보고: "반경 내 경쟁점 어디 갔냐".

수정:
  · 빨간 삼각형은 모든 경쟁점에 항상 그대로 그림 (반경 내/외 색·크기 분기 보존)
  · top5 매장은 그 위에 별도 노란 번호 badge overlay 추가 (yAnchor=1.6 으로 위쪽 띄움)
  · 매장 누락 X — 시각적으로 빨간 삼각형 + 노란 번호 둘 다 보임

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
회귀:
  MarketTab 의 topCompetitors 가 IIFE 라 매 렌더마다 새 배열 참조 생성.
  → MapSection.topCompetitorsForMap useMemo 매 렌더 새 배열
  → MarketMap useEffect deps (topCompetitors) 매 렌더 변경
  → useEffect 재실행 → cleanup 으로 모든 마커 setMap(null) → 재생성 직전 또 deps 바뀜
  → 마커 그려지자마자 지워지는 무한 루프 → 화면에 빨간 삼각형/노란 핀 모두 안 뜸

Fix: useMemo([simResult, _spot1.lat, _spot1.lng]) 로 안정 참조.
     simResult 동일하면 동일 배열 참조 유지 → useEffect 재실행 안 일어남.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
요구:
  사용자 보고 — 풀이 4동 centroid 기준 1.5km 라 spot 1위 좌표 가장자리 매장이
  cap 또는 race condition 으로 누락. spot 좌표 기준으로 모든 분석 일치 필요.

변경:
  · _resolve_spot1_coord(result) helper 추가 — vacancy_spots 의 score 1위 좌표 추출
    (graph._resolve_spot_dong / frontend buildBestVacancies 와 동일 규칙)
  · _query_kakao_store_by_coord(lat, lon, keyword, radius_m, limit) helper 추가
    bounding box → haversine 정확 필터 → ORDER BY kakao_id 결정성 + (distance, id) tie-break
  · _collect_all_competitor_locations 시그니처 확장:
    spot_lat/spot_lon 인자 있으면 spot 좌표 단일 검색 (1.5km, top 400)
    없으면 fallback: 4동 centroid 분기 (구버전 호환, set→sorted 결정성)
  · 호출처 4곳 (predict / analyze/llm / analyze/llm/async / simulate) 에서
    _resolve_spot1_coord 로 spot 좌표 추출 후 인자 전달

효과:
  · 풀 자체가 spot 좌표 기준 → 가장자리 매장 누락 없음
  · 4동 centroid race condition (asyncio.gather + seen_ids) 자동 제거
  · spot 좌표 결정적 (Fix A 효과) + SQL ORDER BY 결정적 → 시뮬마다 동일 결과
  · cap 100→400 으로 spot 좌표 1.5km 안 동종 매장 모두 포함 (마포 카페 ~340개 커버)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
요구:
  사용자 — "공실 spot 1,2,3,4 모두 기준으로 경쟁점 내부/외부 표시"

backend (main.py):
  · _resolve_top_spot_coords(result, max_n=4) helper — 4개 spot 좌표 list 반환
    (frontend buildBestVacancies 와 동일: winner score 정렬 → top3 listing_count → 50m dedup)
  · _collect_all_competitor_locations 에 spot_coords list 인자 추가
    각 spot 별 1.5km × top 400 검색 후 합집합 + dedup (kakao_id 기준)
    결과 정렬: spot1 좌표 기준 거리순 (frontend topCompetitors 와 정합)
  · 호출처 4곳 (predict / analyze/llm / analyze/llm/async / simulate) 모두 spot_coords 전달

frontend:
  · MarketMap within 판정 = 4 spot 중 최단 거리 ≤ radius 면 내부
    targetSpots 그대로 사용 (Layer 4 에서 2~4위 반경원 그릴 때 쓰는 prop 재활용)
  · MapSection legend "내부 X / 총 Y" 카운트도 동일 4 spot union 기준

효과:
  · 4 spot 어느 하나라도 500m 안 매장 = 진한 빨간 삼각형 (내부)
  · 풀 합집합으로 spot2,3,4 가장자리 매장도 포함 (이전 spot1 단일 1.5km 누락 해소)
  · 결정성 유지 — _resolve_top_spot_coords + spot_coords list 모두 결정적, ORDER BY id

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…된 문제

증상:
  4 spot 합집합 풀이 backend 에서 spot1 좌표 거리순 정렬로 반환되는데,
  frontend buildCompetitors cap 200 이 spot1 가까운 매장 200개만 유지 →
  spot 2,3,4 주변 매장은 spot1 기준 멀어서 cap 에 잘려 지도에서 사라짐.

Fix:
  · frontend buildCompetitors cap 200 → 1000 (4 spot × 1.5km 합집합 모두 보존)
  · backend spot_limit 400 → 800 (한 spot 1.5km 안 카페가 400개 넘는 동 대비)

마포 카페 전체가 수백개 수준이라 1000 cap 에 도달하는 일은 사실상 없음.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…t fallback 원인

증상:
  spot 모드 진입했는데 _query_kakao_store_by_coord 가
  ModuleNotFoundError: No module named 'src.database.connection' 발생.
  호출처 try/except 가 exception 잡아 all_competitor_locations=[] 반환.
  frontend 는 samples 20개만 사용하는 else 분기로 빠져 풀이 20개로 보임.

Fix:
  from src.database.connection import get_sync_engine
  → from src.database.sync_engine import get_sync_engine

  실제 모듈 위치 확인 (commercial_intelligence.py 와 동일 import 패턴).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
frontend:
  · MarketMap.tsx — top5 노란 뱃지 클릭 가능 (이전 pointer-events:none 회귀 fix,
    같은 경쟁점 popup 열림)
  · DemographicTab.tsx, FlowVsRevenueScatter.tsx — 이전 세션 demographic 작업분
  · types/index.ts — demographic 스키마 타입 확장

backend:
  · demographic_depth.py — 이전 세션 demographic 분석 로직 변경
  · synthesis.py — 이전 세션 변경
  · schemas/demographic.py — 이전 세션 스키마 확장

TS check + AST parse 통과.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@yezin013 yezin013 merged commit ba03a5e into dev May 6, 2026
1 check passed
@github-actions github-actions Bot changed the title feat(market): 4 spot 기준 경쟁점 표시 통일 + 시뮬 결정성 + AI 요약 레이아웃 IM3-263: feat(market): 4 spot 기준 경쟁점 표시 통일 + 시뮬 결정성 + AI 요약 레이아웃 May 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants